﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Cryptography;
using System.Text;
using viBook.Common;
using viBook.Helpers;
using viBook.Models;

namespace viBook.Repositories
{
    public class UserRepository : IUserRepository
    {
        private const string LocalUrl = "";
        private readonly viBookContext _context = new viBookContext();

        #region IUserRepository Members

        public IQueryable<User> All
        {
            get { return _context.Users; }
        }

        public IQueryable<User> AllIncluding(params Expression<Func<User, object>>[] includeProperties)
        {
            IQueryable<User> query = _context.Users;
            foreach (var includeProperty in includeProperties)
            {
                query = query.Include(includeProperty);
            }
            return query;
        }

        public User Find(int id)
        {
            User user = _context.Users.Find(id);
            if (user == null) return null;
            var listBook = _context.Books.Where(b => b.UploadedBy == id).ToList();
            var listActivities = _context.Activities.Where(a => a.UserId == id).OrderByDescending(a => a.CreatedDate).ToList();
            foreach (var book in listBook)
            {
                //add user
                book.Uploader = _context.Users.Where(u => u.Id == book.UploadedBy).FirstOrDefault();
                if (book.Uploader != null)
                {
                    book.UploaderName = book.Uploader.DisplayName;
                    book.UploaderAvartar = book.Uploader.AvatarUrl;

                }
                //like count
                book.NumberOfLikes = _context.BookLikes.Where(bl => bl.BookId == book.Id).ToList().Count();
                //comment count
                book.NumberOfComments = _context.BookComments.Where(bc => bc.BookId == book.Id).ToList().Count();
            }
            user.IsReported = _context.ReportOnUsers.Where(r => r.ReporteeId == id).Count() > 0;
            user.UploadedBooks = listBook;
            user.Activities = listActivities;
            return user;
        }

        public User FindByEmail(string email)
        {
            foreach (User user in All)
            {
                if (email.Equals(user.Email))
                {
                    user.IsReported = _context.ReportOnUsers.Where(r => r.ReporteeId == user.Id).Count() > 0;
                    return user;
                }
            }
            return null;
        }

        public void InsertOrUpdate(User user)
        {
            if (user.Id == default(int))
            {
                _context.Users.Add(user);
            }
            else
            {
                _context.Entry(user).State = EntityState.Modified;
            }
        }

        public void UpdateProfile(User user)
        {
            User usr = _context.Users.Find(user.Id);
            usr.Location = user.Location;
            usr.Biography = user.Biography;
            usr.DisplayName = user.DisplayName;

            _context.Entry(usr).State = EntityState.Modified;
        }

        public void Delete(string username)
        {
            var user = Find(username);
            if (user == null) return;
            _context.Users.Remove(user);
            Save();
        }

        public User Find(string username)
        {
            return _context.Users.FirstOrDefault(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
        }

        public void Save()
        {
            _context.SaveChanges();
        }

        public bool ValidateUser(string email, string password, out User foundUser)
        {

            IQueryable<User> users = All;
            foundUser = Enumerable.FirstOrDefault(users,
                                                  user =>
                                                  user != null && user.Email == email &&
                                                  user.Password == StringToGuid(password).ToString());
            return foundUser != null;
        }

        public User CreateInternalUser(string username, string displayName, string password, string email)
        {
            if (String.IsNullOrEmpty(username))
                throw new ArgumentException("User name is required.", "UserName");
            if (String.IsNullOrEmpty(password))
                throw new ArgumentException("Password is required.", "Password");
            if (String.IsNullOrEmpty(email)) throw new ArgumentException("Email is required.", "Email");
            if (Enumerable.Any(All, usr => username.Equals(usr.Username)))
            {
                throw new ArgumentException("User name already exists.", "UserName");
            }
            if (Enumerable.Any(All, usr => email.Equals(usr.Email)))
            {
                throw new ArgumentException("Email already exists.", "Email");
            }

            var user = new User
                           {
                               Username = username,
                               DisplayName = displayName,
                               Password = StringToGuid(password).ToString(),
                               Email = email,
                               Status = Constant.StatusCreated,
                               IsExternal = false,
                               JoinedDate = DateTime.Now,
                               LastJoined = DateTime.Now,
                               ValidationCode = GetNewValidationCode()
                           };

            InsertOrUpdate(user);
            Save();
            return user;
        }

        public void Authenticate(string email)
        {
            var u = _context.Users.Where(usr => usr.Email.Equals(email)).FirstOrDefault();
            if (u != null && u.IsExternal)
            {
                u.LastJoined = DateTime.Now;
                _context.Entry(u).State = EntityState.Modified;
                Save();
                return;
            }
            if (u != null) return;
            var user = new User
                           {
                               Email = email,
                               IsExternal = true,
                               JoinedDate = DateTime.Now,
                               LastJoined = DateTime.Now,
                               DisplayName = email
                           };
            InsertOrUpdate(user);
            Save();
        }

        public Guid StringToGuid(string value)
        {
            // Create a new instance of the MD5CryptoServiceProvider object.
            MD5 md5Hasher = MD5.Create();
            // Convert the input string to a byte array and compute the hash.
            byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(value));
            return new Guid(data);
        }

        public User Reactivate(string email)
        {
            User user = FindByEmail(email);
            if (user != null && !user.IsExternal && user.Status.Equals(Constant.StatusCreated))
            {
                user.ValidationCode = GetNewValidationCode();
                InsertOrUpdate(user);
                Save();
                return user;
            }
            return null;
        }

        public bool ValidateActivationCode(int userId, string activationCode)
        {
            User user = Find(userId);
            if (user.Status.Equals(Constant.StatusCreated) && activationCode.Equals(user.ValidationCode))
            {
                user.Status = Constant.StatusApproved;
                user.ValidationCode = "";

                InsertOrUpdate(user);
                Save();
                return true;
            }
            return false;
        }

        public User RequestResetPassword(string email)
        {
            User user = FindByEmail(email);
            if (user != null && !user.IsExternal)
            {
                user.ValidationCode = GetNewValidationCode();
                InsertOrUpdate(user);
                Save();
                return user;
            }
            return null;
        }

        public bool ValidateNewPassword(int userId, string validationCode, string newPassword)
        {
            User user = Find(userId);
            if (validationCode.Equals(user.ValidationCode))
            {
                user.Password = StringToGuid(newPassword).ToString();
                user.ValidationCode = "";
                InsertOrUpdate(user);
                Save();
                return true;
            }
            return false;
        }

        public bool ChangePassword(string email, string oldPassword, string newPassword)
        {
            var user = FindByEmail(email);
            if (StringToGuid(oldPassword).ToString().Equals(user.Password))
            {
                user.Password = StringToGuid(newPassword).ToString();
                InsertOrUpdate(user);
                Save();
                return true;
            }
            return false;
        }

        #endregion

        public bool CheckResourceAccessibility(int userId, ControllerEnum resourceName, int resourceId)
        {
            var user = _context.Users.Find(userId);
            if (user == null) return false;
            if (user.IsAdmin) return true;
            Book book;
            Chapter chapter;
            switch (resourceName)
            {
                case ControllerEnum.Admins:
                case ControllerEnum.Authors:
                case ControllerEnum.Categories:
                case ControllerEnum.ContentTags:
                    return user.IsAdmin;
                case ControllerEnum.Account:
                    return userId == resourceId;
                case ControllerEnum.Books:
                    book = _context.Books.Find(resourceId);
                    return book != null && book.UploadedBy == userId;
                case ControllerEnum.Chapters:
                    chapter = _context.Chapters.Find(resourceId);
                    if (chapter == null) return false;
                    book = _context.Books.Find(chapter.BookId);
                    return book.UploadedBy == userId;
                case ControllerEnum.Pages:
                    var page = _context.Pages.Find(resourceId);
                    if (page == null) return false;
                    chapter = _context.Chapters.Find(page.ChapterId);
                    if (chapter == null) return false;
                    book = _context.Books.Find(chapter.BookId);
                    return book != null && book.UploadedBy == userId;
                case ControllerEnum.Chaptercomments:
                    var chapterComment = _context.ChapterComments.Find(resourceId);
                    return chapterComment.UserId == userId; 
                case ControllerEnum.Bookcomments:
                    var bookComment = _context.BookComments.Find(resourceId);
                    return bookComment.UserId == userId;
                default:
                    throw new Exception("Not supported Controller Enum");
            }
        }

        public void BlockUser(int id)
        {
            var user = Find(id);
            if (user != null)
                user.IsBlocked = true;
            InsertOrUpdate(user);
            Save();
        }

        public void UnblockUser(int id)
        {
            var user = Find(id);
            if (user != null)
                user.IsBlocked = false;
            InsertOrUpdate(user);
            Save();
        }

        public void UpdateAvatar(User user, string newAvatar)
        {
            if (user != null && !String.IsNullOrEmpty(newAvatar))
            {
                user.AvatarUrl = newAvatar;
                InsertOrUpdate(user);
                Save();
            }
        }

        public DataFeedback ReportUser(int reporterId, int reporteeId)
        {
            var feedback = new DataFeedback();
            var existReport = _context.ReportOnUsers.Where(r => r.UserId == reporterId && r.ReporteeId == reporteeId);
            var reportee = _context.Users.Where(u => u.Id == reporteeId).FirstOrDefault();
            if (existReport.Count() == 0)
            {
                _context.ReportOnUsers.Add(new ReportOnUser
                                               {
                                                   IsApproved = false,
                                                   ReportedDate = DateTime.Now,
                                                   UserId = reporterId,
                                                   ReporteeId = reporteeId,
                                                   Reportee = reportee
                                               });
                Save();
                feedback.IsSuccess = true;
            }
            else
            {
                feedback.IsSuccess = false;
                feedback.Message = Constant.ErrorMessage;
            }
            return feedback;
        }

        public IEnumerable<Book> FetchNextBookPage(int userId, int pageNumber)
        {
            int skipItem = (pageNumber - 1) * Constant.MaximunItemsToPaging;
            var listBook =
               _context.Books.Where(b => b.UploadedBy == userId).OrderBy(p => p.NumberOfViews).Skip(skipItem).Take(Constant.MaximunItemsToPaging).ToList();
            foreach (var book in listBook)
            {

                //add user
                book.Uploader = _context.Users.Where(u => u.Id == book.UploadedBy).FirstOrDefault();
                if (book.Uploader != null)
                {
                    book.UploaderName = book.Uploader.DisplayName;
                    book.UploaderAvartar = book.Uploader.AvatarUrl;

                }
                //like count
                book.NumberOfLikes = _context.BookLikes.Where(bl => bl.BookId == book.Id).ToList().Count();
                //comment count
                book.NumberOfComments = _context.BookComments.Where(bc => bc.BookId == book.Id).ToList().Count();

            }
            return listBook;
        }

        public IEnumerable<User> GetMostPopularUsers()
        {
            var listuser = _context.Users.Where(u=>u.IsBlocked==false).ToList();
            foreach (var user in listuser)
            {
                user.NumberOfUploadedBook = _context.Books.Where(b => b.UploadedBy == user.Id).Count();
            }
            return listuser.OrderByDescending(u => u.NumberOfUploadedBook).Take(Constant.MaximunPopUSer);
        }

        public IEnumerable<Activity> FetchNextActivitiesPage(int userId, int pageNumber)
        {
            int skipItem = (pageNumber - 1) * Constant.MaximumActivitiesToPaging;
            var listActivities =
                _context.Activities.Where(a => a.UserId == userId).OrderByDescending(a => a.CreatedDate).Skip(skipItem).
                    Take(Constant.MaximumActivitiesToPaging).ToList();

            return listActivities;
        }

        private string GetNewValidationCode()
        {
            long i = Guid.NewGuid().ToByteArray().Aggregate<byte, long>(1, (current, b) => current * (b + 1));

            return string.Format("{0:x}", i - DateTime.Now.Ticks);
        }

        public static string RpHash(string value)
        {
            int hash = 5381;
            value = value.ToUpper();
            for (int i = 0; i < value.Length; i++)
            {
                hash = ((hash << 5) + hash) + value[i];
            }
            return hash.ToString();
        }
    }

    public interface IUserRepository
    {
        IQueryable<User> All { get; }
        IQueryable<User> AllIncluding(params Expression<Func<User, object>>[] includeProperties);
        User Find(int id);
        User Find(string username);
        User FindByEmail(string email);
        void InsertOrUpdate(User user);
        void UpdateProfile(User user);
        void Delete(string username);
        void Save();
        bool ValidateUser(string email, string password, out User foundUser);
        User CreateInternalUser(string username, string displayName, string password, string email);
        void Authenticate(string email);
        Guid StringToGuid(string value);
        User Reactivate(string email);
        bool ValidateActivationCode(int userId, string activationCode);
        User RequestResetPassword(string email);
        bool ValidateNewPassword(int userId, string validationCode, string newPassword);
        bool ChangePassword(string email, string oldPassword, string newPassword);
        bool CheckResourceAccessibility(int userId, ControllerEnum resourceName, int resourceId);
        void BlockUser(int id);
        void UnblockUser(int id);
        void UpdateAvatar(User user, string newAvatar);
        DataFeedback ReportUser(int reporterId, int reporteeId);
        IEnumerable<Book> FetchNextBookPage(int userId, int pageNumber);
        IEnumerable<User> GetMostPopularUsers();
        IEnumerable<Activity> FetchNextActivitiesPage(int userId, int pageNumber);
    }
}
